package com.bigflexlib.ds.container.impl
{
	
	import flash.events.Event;
	import flash.events.EventDispatcher;
	import flash.utils.Dictionary;
	
	import com.bigflexlib.core.comparison.result.ComparisonResult;
	import com.bigflexlib.ds.DataStructureEvent;
	import com.bigflexlib.ds.container.ContainerDiff;
	import com.bigflexlib.ds.container.IOWContainer;
	import com.bigflexlib.ds.container.IRWContainer;
	import com.bigflexlib.ds.container.utils.ContainerUtil;
	import com.bigflexlib.ds.container.utils.MultiplicityChange;
	import com.bigflexlib.utils.AutoUpdateFlag;





/**
 *  
 * 
 *  @eventType com.bigflexlib.ds.DataStructureEvent.CHANGE
 */
[Event(name="change", type="com.bigflexlib.ds.DataStructureEvent")]	




	/**
	 * 
	 * 
	 * if someoneIsWatching( )
	 * 
	 * call
	 * 
	 * beforeChange and afterChange
	 * 
	 * to make sure your changes get loged and the corresponding events are dispatched
	 * 
	 * @author aldo.bucchi@gmail.com
	 * 
	 */
	public class AbstractOWContainerWrapper extends EventDispatcher implements IOWContainer
	{


		/**
		 * 
		 * This is where we hold the multiplicity changes in the meantime
		 * 
		 */
		private var dict:Dictionary = new Dictionary( );



		/**
		 * 
		 * 
		 * 
		 */
		private var internalContainer:IRWContainer;




		private var _autoUpdateFlag:AutoUpdateFlag;

		public function get autoUpdateFlag():AutoUpdateFlag
		{
			return _autoUpdateFlag;
		}

		
		
		protected function get someoneIsWatching( ):Boolean
		{
			return hasEventListener( DataStructureEvent.CHANGE );
		}
		
		
		public function AbstractOWContainerWrapper( internalContainer:IRWContainer )
		{

			this.internalContainer = internalContainer;
			
			_autoUpdateFlag = new AutoUpdateFlag( flush );

		}
		
		
		
		
		////////////////////////////////////////////////////////
		// events
		////////////////////////////////////////////////////////
		
		
		
		override public function addEventListener( type:String, listener:Function, useCapture:Boolean=false, priority:int=0.0, useWeakReference:Boolean=false):void
		{
			

			// we flush any queued messages before adding an event listener
			// this is necessary to prevent the new listener from receiving
			// a diff with an incorrect time span ( one that contains updates prior to this moment )
			// NOTE: I think that this is something that Adobe overlooked in ArrayCollection				
			
			flush( );
		
			super.addEventListener( type, listener, useCapture, priority, useWeakReference );
		
		}
		
		
		
		
		
		
		
		////////////////////////////////////////////////////////
		///////// update and consolidation logic ///////////////
		////////////////////////////////////////////////////////		
		
		
		
		private var oldM:Dictionary = new Dictionary( );
		
		private var newM:Dictionary = new Dictionary( );		

		
		
		private function flush( ):void
		{

			var multiplicityChanges:Dictionary = new Dictionary( );
			var thereAreChanges:Boolean = false;

			for ( var e:* in oldM )
			{
				var oldMultiplicity:uint = uint( oldM[ e ] );
				var newMultiplicity:uint = uint( newM[ e ] );
				
				if ( oldMultiplicity != newMultiplicity )
				{
					multiplicityChanges[ e ] = new MultiplicityChange( oldMultiplicity, newMultiplicity );
					thereAreChanges = true;			
				}
				
			}


			// reset storage BEFORE dispatching the event
			// this is needed to avoid the case where the event triggers some code
			// that adds an event listener and we end up calling flush() again
			// entering an infinite loop
			oldM = new Dictionary( );
			newM = new Dictionary( );



			// dispatch the event
			// TODO: we need to find a way for subclasses ( bag, set, sequence ) to provide their own event 
			// or diff with extra info
			if ( thereAreChanges )
			{
				var diff:ContainerDiff = new ContainerDiff( multiplicityChanges );
				dispatchEvent( new DataStructureEvent( DataStructureEvent.CHANGE, diff ) );
			}			



			
		}
		

		
		
		
		/**
		 * 
		 * In your concrete implementations, call this method before changing the multiplicity
		 * of element "e" to ensure that the current multiplicity is recorded.
		 * 
		 * If you don't explicitly pass the current multiplicity it will be looked up.
		 * You are given the option to pass it so you can eliminate the extra lookup if possible.
		 * 
		 * @param e
		 * @param currentMultiplicity
		 * 
		 */		
		protected function beforeChange( e:*, currentMultiplicity:int = -1 ):void
		{
		
			if ( isNaN( oldM[ e ] ) ) // otherwise it is already recorded
			{
				
				if ( currentMultiplicity == -1 )
					currentMultiplicity = internalContainer.getMultiplicity( e );
	
				oldM[ e ] = currentMultiplicity;
			
			}
		
		}
		
		


		/**
		 * 
		 * 
		 * 
		 * @param e
		 * @param newMultiplicity
		 * 
		 */		
		protected function afterChange( e:*, newMultiplicity:int = -1 ):void
		{
			
			if ( newMultiplicity == -1 )
				newMultiplicity = internalContainer.getMultiplicity( e );
			
			newM[ e ] = newMultiplicity;				
			
			_autoUpdateFlag.requestUpdate( );
				
		}
		
		
		
		
		

		
		

		
		
		
		
		////////////////////////////////////////////////////////
		///////////////// write ( intercept ) //////////////////
		////////////////////////////////////////////////////////



		public function clear( ):void
		{
			
			if ( someoneIsWatching )
			{
				_autoUpdateFlag.disable( );			
				internalContainer.walk( clearWalkerFunction1 );
				internalContainer.clear( );

				for ( var e:* in oldM ) // we set all multiplicities to zero ( this could be further optimized )
				{
					afterChange( e, 0 );
				}


				_autoUpdateFlag.enable( );			
			}
			else
			{
				internalContainer.clear( );
			}
			
		}
		
		
		
		private function clearWalkerFunction1( e:*, multiplicity:uint ):void
		{
			beforeChange( e );
		}
		
		
		
		
		
		public function remove( e:* ):void
		{
			if ( someoneIsWatching )
			{
				beforeChange( e );
				internalContainer.remove( e );
				afterChange( e, 0 );			
			}
			else
			{
				internalContainer.remove( e );
			}
		}
		
		
		
		
		
		
		////////////////////////////////////////////////////////
		///////////////// read ( pass thru ) ///////////////////
		////////////////////////////////////////////////////////
		
		[Bindable("change")]
		public function get isEmpty( ):Boolean
		{ 
			return internalContainer.isEmpty;
		}
		
		
		[Bindable("change")]
		public function get size( ):uint
		{
			return internalContainer.size;
		}		
		
		
		public function contains( e:* ):Boolean
		{
			return internalContainer.contains( e );
		}
		
		
		public function walk( walker:Function ):void
		{
			internalContainer.walk( walker );			
		}
		

		public function getMultiplicity( e:* ):uint
		{
			return internalContainer.getMultiplicity( e );
		}
		

		public function any( emptyValue:* = undefined ):*
		{
			return internalContainer.any( emptyValue );
		}


//		public function toArray( ):Array
//		{
//			return internalContainer.toArray( );
//		}		
		
		
		public function get asArray( ):Array
		{
			return internalContainer.asArray;
		}
		
		
		
		public function compare( other:* ):ComparisonResult
		{
		
			return ContainerUtil.doCompare( this, other );
		
		}
		
		
		

		override public function toString( ):String
		{
			
			return ContainerUtil.toArray( this ).join(",");		
		
		}		
		
		
		
		
	}
}